package net.baiken.commons;

import java.util.Enumeration;
import java.util.IllegalFormatException;
import java.util.Locale;
import java.util.MissingResourceException;
import java.util.ResourceBundle;

/**
 * This class allow to access to message resources by the current {@link Locale}
 * . When the key does not exist the class returns an error missing message
 * instead of message. And when the formatted message is called and if the
 * format is incorrect the class returns a format error instead of message.
 * 
 * @author Baiken
 * 
 */
public class Messages {

	/**
	 * Format for the missing error message.
	 */
	private static final String MISSING_MESSAGE_FORMAT = "[MissingMessageError] %s";

	/**
	 * format for the format error message.
	 */
	private static final String ERROR_MESSAGE_FORMAT = "[FormatError] %s";

	/**
	 * Singleton instance
	 */
	private static Messages instance;

	/**
	 * Base name to find the message resource
	 */
	private String baseName = null;

	/**
	 * Resource bundle for this object
	 */
	private ResourceBundle parentResourceBundle;

	/**
	 * It returns the unique instance of this class. It requires no null
	 * {@link Messages#baseName}.
	 * 
	 * @throws RuntimeException
	 *             If {@link Messages#baseName} is <code>null</code>
	 * 
	 * @return Unique instance of {@link Messages}
	 */
	public static Messages getInstance() {
		if (instance == null) {
			instance = new Messages();
		}
		return instance;
	}

	/**
	 * Private constructor
	 */
	private Messages() {

	}

	/**
	 * Validates if the base name was not null
	 */
	private void load() {
		validatePreferecesObject();
		Locale currentLocale = Locale.getDefault();
		try {
			parentResourceBundle = ResourceBundle.getBundle(this.baseName,
					currentLocale);
		} catch (MissingResourceException e) {
			parentResourceBundle = ResourceBundle.getBundle(this.baseName);
		}
	}

	/**
	 * @return list of key names in current message resource
	 */
	public Enumeration<String> getKeys() {
		validatePreferecesObject();
		return parentResourceBundle.getKeys();
	}

	/**
	 * @param enumKey
	 *            key of the message in message resources
	 * @return message in the message resources. If the message does not exist
	 *         it returns a missing error message.
	 */
	public String getString(Enum<?> enumKey) {
		validatePreferecesObject();
		String returnValue;
		String key = getName(enumKey);
		if (parentResourceBundle.containsKey(key)) {
			returnValue = parentResourceBundle.getString(key);
		} else {
			returnValue = String.format(MISSING_MESSAGE_FORMAT, key);
		}
		return returnValue;
	}

	/**
	 * @param enumKey
	 *            key of the message in message resources
	 * @param params
	 *            parameters to replace in the string format. The string format
	 *            in message resource will be a
	 *            {@link String#format(String, Object...)} supported string
	 *            formatter
	 * @return
	 */
	public String getFormattedString(Enum<?> enumKey, Object... params) {
		validatePreferecesObject();
		String returnValue;
		String key = getName(enumKey);
		if (parentResourceBundle.containsKey(key)) {
			try {
				returnValue = String.format(getString(enumKey), params);
			} catch (IllegalFormatException e) {
				returnValue = String.format(ERROR_MESSAGE_FORMAT, key);
			}
		} else {
			returnValue = String.format(MISSING_MESSAGE_FORMAT, key);
		}
		return returnValue;
	}

	/**
	 * @param enumKey
	 *            {@link Enum} value to process
	 * @return a prepared name for the enum
	 */
	private String getName(Enum<?> enumKey) {
		validatePreferecesObject();
		return enumKey.getDeclaringClass().getName().replace(
				enumKey.getClass().getPackage().getName(), "").replaceAll(
				"\\$", "_").substring(1).concat("_").concat(enumKey.name())
				.toLowerCase();
	}

	/**
	 * It sets the value for the base name. If the base name had been set it
	 * throws a {@link IllegalAccessError}.
	 * 
	 * @param baseName
	 *            name to set
	 * @throws IllegalAccessError
	 *             If the base name had value and it method is called again
	 */
	public void setBaseName(String baseName) {
		if (baseName == null) {
			throw new IllegalAccessError(
					"The base name already was set. You cannot change at this time.");
		} else {
			this.baseName = baseName;
			load();
		}

	}

	/**
	 * @return base name for find the message resource
	 */
	public String getBaseName() {
		return baseName;
	}

	/**
	 * It validates if the base name is no null
	 */
	private void validatePreferecesObject() {
		if (baseName == null) {
			throw new RuntimeException("BaseName cannot be null");
		}
	}
}
